package xredis

import (
	"context"
	"fmt"
	"time"

	"github.com/redis/go-redis/v9"
)

const (
	defaultLockVal = 1
)

// RedisLocker Redis 锁
type RedisLocker struct {
	client     *Client
	key        string
	retries    int
	interval   time.Duration
	expiration time.Duration
}

func NewRedisLocker(client *Client, key string, keyFmtArgs ...interface{}) *RedisLocker {
	if len(keyFmtArgs) > 0 {
		key = fmt.Sprintf(key, keyFmtArgs...)
	}
	return &RedisLocker{
		client:     client,
		key:        key,
		retries:    3,                      // 默认进行 5 次重试
		interval:   100 * time.Millisecond, // 默认间隔 100ms
		expiration: 1 * time.Second,        // 默认过期 1s
	}
}

func (l *RedisLocker) Key() string {
	return l.client.KeyPrefix() + l.key
}

func (l *RedisLocker) WithConfig(retries int, interval, expiration time.Duration) *RedisLocker {

	if retries < 2 {
		retries = 1
	}

	if min := 5 * time.Millisecond; interval < min {
		interval = min
	}

	l.retries = retries
	l.interval = interval
	l.expiration = expiration

	return l
}

func (l *RedisLocker) Lock(ctx context.Context, values ...interface{}) bool {
	var val interface{}
	val = defaultLockVal
	if len(values) > 0 {
		val = values[0]
	}
	for i := 0; i <= l.retries; i++ {
		ok, err := l.client.SetNX(ctx, l.key, val, l.expiration).Result()
		if err == nil && ok {
			return true
		}
		time.Sleep(l.interval)
	}
	return false
}

func (l *RedisLocker) Unlock(ctx context.Context, values ...interface{}) bool {
	var val interface{}
	val = defaultLockVal
	if len(values) > 0 {
		val = values[0]
	}
	lua := `
	local value=redis.call("GET", KEYS[1])
	if(value=="%v") then
		 return redis.call("DEL", KEYS[1])
	end
`
	script := redis.NewScript(fmt.Sprintf(lua, val))
	for i := 0; i <= l.retries; i++ {
		err := script.Run(ctx, l.client, []string{l.Key()}).Err()
		if err == nil || err == redis.Nil {
			return true
		}
		time.Sleep(l.interval)
	}
	return false
}
